package com.iwhalecloud.citybrain.flow.platform.manager.application.service.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import com.alibaba.fastjson.JSONObject;
import com.iwhalecloud.citybrain.flow.platform.manager.application.common.event.CreateParticipantEvent;
import com.iwhalecloud.citybrain.flow.platform.manager.application.common.threadlocal.CommonOperateLogHolder;
import com.iwhalecloud.citybrain.flow.platform.manager.application.utils.FlowUtils;
import com.iwhalecloud.citybrain.flow.platform.manager.application.utils.SpringUtils;
import com.iwhalecloud.citybrain.flow.platform.manager.application.utils.StringUtils;
import com.iwhalecloud.citybrain.flow.platform.manager.common.ErrorCode;
import com.iwhalecloud.citybrain.flow.platform.manager.common.client.HttpCommandDto;
import com.iwhalecloud.citybrain.flow.platform.manager.common.client.HttpCommandResultDto;
import com.iwhalecloud.citybrain.flow.platform.manager.common.client.HttpTaskRollbackDto;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.FlowActivityJumpHistoryVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.repository.FlowActivityJumpHistoryRepository;
import org.apache.commons.collections4.CollectionUtils;
import org.flowable.bpmn.model.BaseElement;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.impl.RuntimeServiceImpl;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * Handler - 任务回退
 *
 * @author lilh
 * @date 2019-07-10 09:58
 */
@Service
public class TaskRollbackCommandHandler extends AbstractCommandHandler {

    @Autowired
    private HistoryService historyService;

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private FlowActivityJumpHistoryRepository flowActivityJumpHistoryRepository;

    @Override
    public String handleType() {
        return TASK_ROLLBACK;
    }

    @Override
    public HttpCommandResultDto handle(String params) {
        HttpTaskRollbackDto httpTaskRollbackDto = JSONObject.parseObject(params, HttpTaskRollbackDto.class);
        return handle(httpTaskRollbackDto);
    }

    @Override
    public HttpCommandResultDto handle(HttpCommandDto commandDto) {
        HttpTaskRollbackDto httpTaskRollbackDto = (HttpTaskRollbackDto) commandDto;
        CommonOperateLogHolder.getContent().setRequest(httpTaskRollbackDto);
        //1.参数校验，传递的流程实例是否有效
        ProcessInstance processInstance = resolveProcessInstance(httpTaskRollbackDto.getProcessInstanceId());
        if (Objects.isNull(processInstance)) {
            logger.warn("流程实例不存在{}", httpTaskRollbackDto.getProcessInstanceId());
            return resolveResult((String) null, ErrorCode.PROCESS_INSTANCE_NOT_EXIST.errMessage(), null);
        }

        //检验操作人员信息
        if (org.apache.commons.lang3.StringUtils.isBlank(httpTaskRollbackDto.getParticipantName())) {
            logger.warn("没有传递操作人员信息");
            return resolveFailResult(httpTaskRollbackDto.getProcessInstanceId(), "缺少操作人员信息");
        }
        //2.查找当前运行中的节点，即回退的源节点信息
        List<Execution> sourceExecutionEntities = runtimeService.createExecutionQuery().processInstanceId(httpTaskRollbackDto.getProcessInstanceId()).onlyChildExecutions().list();
        if (CollectionUtils.isEmpty(sourceExecutionEntities)) {
            logger.warn("找不到待回退的源节点信息，流程实例id为：{}", httpTaskRollbackDto.getProcessInstanceId());
            return resolveFailResult(httpTaskRollbackDto.getProcessInstanceId(), ErrorCode.TASK_ROLLBACK_FAILED.errMessage());
        }
        List<String> sourceActivityIds = sourceExecutionEntities.stream().map(Execution::getActivityId).collect(Collectors.toList());

        //3.1 若指定了targetActivities，则判断这些值是否有执行过，即查找任务历史表
        List<String> finalTargetActivityIds = resolveFinalTargetActivityIds(httpTaskRollbackDto, processInstance, sourceActivityIds);

        if (CollectionUtils.isEmpty(finalTargetActivityIds)) {
            logger.warn("目标节点不存在,或者不能回退到自身节点,或者该节点未被处理过");
            return resolveFailResult(httpTaskRollbackDto.getProcessInstanceId(), ErrorCode.TASK_ROLLBACK_FAILED.errMessage());
        }

        new CustomChangeActivityStateBuilderImpl((RuntimeServiceImpl) runtimeService).moveActivityIdsToSingleActivityIds(sourceActivityIds, finalTargetActivityIds).processInstanceId(httpTaskRollbackDto.getProcessInstanceId()).changeState();
        recordJumpHistory(sourceActivityIds, finalTargetActivityIds, processInstance, httpTaskRollbackDto);
        return resolveResult(httpTaskRollbackDto.getProcessInstanceId(), null, TASK_ROLLBACK_SUCCESS);
    }

    private void recordJumpHistory(List<String> sourceActivityIds, List<String> finalTargetActivityIds, ProcessInstance processInstance, HttpTaskRollbackDto httpTaskRollbackDto) {
        //1.记录人员信息
        recordParticipant(httpTaskRollbackDto);

        //2.记录节点跳转信息
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
        for (String source : sourceActivityIds) {
            FlowElement sourceElement = bpmnModel.getFlowElement(source);
            for (String target : finalTargetActivityIds) {
                FlowElement targetElement = bpmnModel.getFlowElement(target);
                FlowActivityJumpHistoryVo record = new FlowActivityJumpHistoryVo();
                record.setProcessInstanceId(processInstance.getProcessInstanceId());
                record.setFromActivityId(sourceElement.getId());
                record.setFromActivityName(sourceElement.getName());
                record.setToActivityId(targetElement.getId());
                record.setToActivityName(targetElement.getName());
                record.setComment(httpTaskRollbackDto.getComment());
                record.setCreateName(httpTaskRollbackDto.getParticipantName());
                flowActivityJumpHistoryRepository.insertActivityJumpHistory(record);
            }
        }

    }

    private void recordParticipant(HttpTaskRollbackDto httpTaskRollbackDto) {
        CreateParticipantEvent.CreateParticipantVo createParticipantVo = new CreateParticipantEvent.CreateParticipantVo();
        createParticipantVo.setParticipantId(httpTaskRollbackDto.getParticipantId());
        createParticipantVo.setParticipantType(httpTaskRollbackDto.getParticipantType());
        createParticipantVo.setParticipantName(httpTaskRollbackDto.getParticipantName());
        SpringUtils.publishEvent(new CreateParticipantEvent(createParticipantVo));
    }

    private List<String> resolveFinalTargetActivityIds(HttpTaskRollbackDto httpTaskRollbackDto, ProcessInstance processInstance, List<String> sourceActivityIds) {
        List<String> result = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(httpTaskRollbackDto.getTargetActivities())) {
            //若指定了回退的目标则校验指定的目标节点是否执行过
            for (String target : httpTaskRollbackDto.getTargetActivities()) {
                if (StringUtils.isBlank(target) || sourceActivityIds.contains(target)) {
                    continue;
                }
                long count = historyService.createHistoricTaskInstanceQuery().processInstanceId(httpTaskRollbackDto.getProcessInstanceId()).taskDefinitionKey(target).count();
                if (count > 0) {
                    result.add(target);
                }
            }
        } else {
            //没有指定回退目标
            List<FlowElement> foundElements = new ArrayList<>();
            for (String activityId : sourceActivityIds) {
                foundElements.addAll(FlowUtils.getInstance().getFirstFrontElement(activityId, processInstance));
            }
            Set<String> foundActivityIds = foundElements.stream().map(BaseElement::getId).collect(Collectors.toSet());
            for (String activityId : foundActivityIds) {
                long count = historyService.createHistoricTaskInstanceQuery().processInstanceId(httpTaskRollbackDto.getProcessInstanceId()).taskDefinitionKey(activityId).count();
                if (count > 0) {
                    result.add(activityId);
                }
            }
        }

        return result;
    }
}
